/**
 * 
 */
package com.jd.ipservice;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Arrays;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.util.SubnetUtils;

/**
 * @author LeoQin
 *
 */
public class BinaryIpService implements IpService {
	private Log logger = LogFactory.getLog(this.getClass());

	private final Map<Long, Cidr> _cidrs;
	private final Long[] _startsArray;
	private final Map<Long, Cidr> _endcidrs;
	private final Long[] _endsArray;
	private final Map<String, NamePair> _countryCnNames;
	private final Map<String, NamePair> _ispCnNames;
	private final Map<String, NamePair> _provinceCnNames;

	@SuppressWarnings("unchecked")
	public BinaryIpService() throws IllegalArgumentException, IOException, ClassNotFoundException {
		super();
		_countryCnNames = (Map<String, NamePair>) new ObjectInputStream(this.getClass()
				.getResourceAsStream("/serial/countryCnNames.serial")).readObject();
		_ispCnNames = (Map<String, NamePair>) new ObjectInputStream(this.getClass()
				.getResourceAsStream("/serial/ispCnNames.serial")).readObject();
		_provinceCnNames = (Map<String, NamePair>) new ObjectInputStream(this.getClass()
				.getResourceAsStream("/serial/provinceCnNames.serial")).readObject();
		_startsArray = (Long[]) new ObjectInputStream(this.getClass().getResourceAsStream(
				"/serial/cidr_starts.serial")).readObject();
		_cidrs = (Map<Long, Cidr>) new ObjectInputStream(this.getClass().getResourceAsStream(
				"/serial/cidrs.serial")).readObject();
		_endsArray = (Long[]) new ObjectInputStream(this.getClass().getResourceAsStream(
				"/serial/cidr_ends.serial")).readObject();
		_endcidrs = (Map<Long, Cidr>) new ObjectInputStream(this.getClass().getResourceAsStream(
				"/serial/end_cidrs.serial")).readObject();

	}

	@SuppressWarnings("unused")
	private static long[] getRange(String line) {
		SubnetUtils su = new SubnetUtils(line);
		long[] ranges = new long[2];
		ranges[0] = toLong(su.getInfo().getLowAddress());
		ranges[1] = toLong(su.getInfo().getHighAddress());
		return ranges;
	}

	private static long toLong(String strIP) {
		long[] ip = new long[4];
		// ���ҵ�IP��ַ�ַ���.��λ��
		int position1 = strIP.indexOf(".");
		int position2 = strIP.indexOf(".", position1 + 1);
		int position3 = strIP.indexOf(".", position2 + 1);
		// ��ÿ��.֮����ַ�ת��������
		ip[0] = Long.parseLong(strIP.substring(0, position1));
		ip[1] = Long.parseLong(strIP.substring(position1 + 1, position2));
		ip[2] = Long.parseLong(strIP.substring(position2 + 1, position3));
		ip[3] = Long.parseLong(strIP.substring(position3 + 1));
		return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];
	}

	public IpInfo getIpInfo(String ip) {
		IpInfo ipInfo = new IpInfo();
		Cidr cidr = leftFind(ip);
		if (cidr == null) {
			cidr = rightFind(ip);
			if (cidr == null) {
				logger.debug("ipinfo not found. ip:" + ip);
				return IpInfo.getOther();
			}
		}
		String[] names = StringUtils.split(cidr.getIpSet().getName(), "-");
		String country = StringUtils.substringAfterLast(names[0], "\"");
		if (_countryCnNames.containsKey(country)) {
			ipInfo.setCountry(_countryCnNames.get(country).getName());
		} else {
			ipInfo.setCountry(FAConstants.IP_OTHER);
		}
		String isp = names[1];
		if (_ispCnNames.containsKey(isp)) {
			ipInfo.setIsp(_ispCnNames.get(isp).getName());
		} else {
			ipInfo.setIsp(FAConstants.IP_OTHER);
		}
		String province = StringUtils.substringBefore(names[2], "\"");
		if (_provinceCnNames.containsKey(province)) {
			ipInfo.setProvince(_provinceCnNames.get(province).getName());
		} else {
			if (!province.equals("OTHER"))
				logger.debug("province not found. province:" + province);
			ipInfo.setProvince(FAConstants.IP_OTHER);
		}
		ipInfo.setCountryEn(country);
		ipInfo.setIspEn(isp);
		ipInfo.setProvinceEn(province);
		return ipInfo;

	}

	private Cidr leftFind(String ip) {
		long qip = toLong(ip);
		int offset = Arrays.binarySearch(_startsArray, qip);
		int index = offset < -1 ? Math.abs(offset) - 2 : offset;
		if (index < _startsArray.length && index > -1) {
			Cidr cidr = _cidrs.get(_startsArray[index]);
			if (cidr.contains(qip)) {
				return cidr;
			} else {
				logger.debug("unknown ip not in range of cidr. " + ip + " " + cidr.getCidr());
				return null;
			}
		} else {
			logger.debug("unknown ip index out of address range. " + ip);
			return null;
		}
	}

	private Cidr rightFind(String ip) {
		long qip = toLong(ip);
		int offset = Arrays.binarySearch(_endsArray, qip);
		int index = offset < -1 ? Math.abs(offset) - 1 : offset;
		if (index < _endsArray.length && index > -1) {
			Cidr cidr = _endcidrs.get(_endsArray[index]);
			if (cidr.contains(qip)) {
				return cidr;
			} else {
				logger.debug("unknown ip not in range of cidr. " + ip + " " + cidr.getCidr());
				return null;
			}
		} else {
			logger.debug("unknown ip index out of address range. " + ip);
			return null;
		}
	}
}

